//
// Copyright (c) 2007 Rodrigo B. de Oliveira & Jb Evain
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//

using System;
using System.Diagnostics;
using System.IO;
using SR = System.Reflection;
using Boo.Lang.Compiler;
using Boo.Lang.Compiler.IO;
using Mono.Cecil;
using NUnit.Framework;

namespace Milo.Compilation.Tests
{
	[TestFixture]
	public class BooIntegrationTestFixture
	{
		[TestFixtureSetUp]
		public void FixtureSetUp()
		{
			CopyEnclosingAssemblyTo(typeof(Boo.Lang.Runtime.RuntimeServices), Path.GetTempPath());
		}

		private static void CopyEnclosingAssemblyTo(Type type, string targetDir)
		{
			string fileName = type.Module.FullyQualifiedName;
			File.Copy(fileName, Path.Combine(targetDir, Path.GetFileName(fileName)), true);
		}

		[Test]
		public void Boxing()
		{
			RunTestCase("Boxing.boo");
		}

		[Test]
		public void MethodCallDiscardingReturnedValue()
		{
			RunTestCase("MethodCallDiscardingReturnedValue.boo");
		}

		[Test]
		public void FinalField()
		{
			AssemblyDefinition assembly = RunTestCase("FinalField.boo");
			TypeDefinitionCollection types = assembly.MainModule.Types;
			// FinalFieldModule, Foo, <Module>
			Assert.AreEqual(3, types.Count);

			TypeDefinition foo = types["FinalField.Foo"];
			Assert.IsNotNull(foo);

			FieldDefinition field = foo.Fields.GetField("_bar");
			Assert.IsNotNull(field);

			Assert.IsTrue(field.IsReadOnly);
		}

		[Test]
		public void Downcasting()
		{
			RunTestCase("Downcasting.boo");
		}

		[Test]
		public void LocalVariable()
		{
			AssemblyDefinition assembly = RunTestCase("LocalVariable.boo");
			TypeDefinition module = assembly.MainModule.Types["LocalVariableModule"];
			Assert.IsNotNull(module);
			MethodDefinition main = module.Methods.GetMethod("Main")[0];
			Assert.IsNotNull(main);
			Assert.IsNotNull(main.Body);
			Assert.AreEqual(1, main.Body.Variables.Count);
		}

		[Test]
		public void Statics()
		{
			AssemblyDefinition assembly = RunTestCase("Statics.boo");
			TypeDefinition foo = assembly.MainModule.Types["Foo"];
			Assert.IsNotNull(foo);
			FieldDefinition bar = foo.Fields.GetField("Bar");
			Assert.IsNotNull(bar);
			Assert.IsTrue(bar.IsStatic);
			MethodDefinition baz = foo.Methods.GetMethod("Baz")[0];
			Assert.IsNotNull(baz);
			Assert.IsTrue(baz.IsStatic);
		}

		[Test]
		public void TestInheritance()
		{
			AssemblyDefinition asm = RunTestCase("Inheritance.boo");
			TypeDefinition foo = asm.MainModule.Types["Foo"];
			TypeDefinition bar = asm.MainModule.Types["Bar"];
			TypeDefinition baz = asm.MainModule.Types["Baz"];

			Assert.AreEqual(foo, bar.BaseType);
			Assert.AreEqual("System.Collections.CollectionBase", baz.BaseType.FullName);
		}

		[Test]
		public void TestInterfaces()
		{
			AssemblyDefinition asm = RunTestCase("Interfaces.boo");
			TypeDefinition iboo = asm.MainModule.Types["IBoo"];
			TypeDefinition ifoo = asm.MainModule.Types["IFoo"];
			TypeDefinition foo = asm.MainModule.Types["ConcreteFoo"];

			Assert.IsTrue(iboo.IsInterface);
			Assert.IsTrue(iboo.IsAbstract);

			MethodDefinition boo = iboo.Methods.GetMethod("Boo")[0];
			Assert.IsTrue(boo.IsVirtual);
			Assert.IsTrue(boo.IsAbstract);

			Assert.IsTrue(ifoo.Interfaces.Contains(iboo));
			Assert.IsTrue(foo.Interfaces.Contains(ifoo));
		}

		[Test]
		public void TestEmitAssemblyInMemory()
		{
			string simple = @"
class Foo:
	pass
";
			SR.Assembly assembly = CompileToMemory(simple);
			Assert.IsNotNull(assembly.GetType("Foo"));
		}

		private static AssemblyDefinition RunTestCase(string file)
		{
			string path = GetTestCasePath(file);
			AssemblyDefinition assembly = Compile(path);
			Verify(assembly);
			ExecuteAndAssertOutput(assembly, path);
			return assembly;
		}

		private static void ExecuteAndAssertOutput(AssemblyDefinition assembly, string path)
		{
			string output = RunAssembly(assembly);
			Assert.AreEqual(NormalizeWhitespace(GetExpectedOutput(path)), NormalizeWhitespace(output));
		}

		private static string RunAssembly(AssemblyDefinition assembly)
		{
			if (OnMono())
			{
				return Boo.Lang.Builtins.shell("mono", GetAssemblyPath(assembly));
			}
			return Boo.Lang.Builtins.shell(GetAssemblyPath(assembly), string.Empty);
		}

		private static string GetAssemblyPath(AssemblyDefinition assembly)
		{
			return assembly.MainModule.Image.FileInformation.FullName;
		}

		private static bool OnMono()
		{
			return typeof(object).Assembly.GetType("System.MonoType") != null;
		}

		private static string NormalizeWhitespace(string s)
		{
			return s.Trim().Replace("\r\n", "\n");
		}

		private static string GetExpectedOutput(string path)
		{
			return Boo.Lang.Parser.BooParser.ParseFile(path).Modules[0].Documentation;
		}

		private static void Verify(AssemblyDefinition assembly)
		{
			if (OnMono())
			{
				MonoVerify(assembly);
			}
			else
			{
				NetVerify(assembly);
			}
		}

		private static void NetVerify(AssemblyDefinition assembly)
		{
			AssertTool("peverify", "/MD /IL {0}", assembly);
		}

		private static string Quote(string s)
		{
			return "\"" + s + "\"";
		}

		private static void MonoVerify(AssemblyDefinition assembly)
		{
			AssertTool("pedump", "--verify all {0}", assembly);
		}

		static void AssertTool (string tool, string command, AssemblyDefinition assembly)
		{
			Process p = Boo.Lang.Builtins.shellp(tool, string.Format(command, Quote(GetAssemblyPath(assembly))));
			p.WaitForExit();
			if (p.ExitCode == 0) return;

			Assert.Fail(p.StandardOutput.ReadToEnd());
		}

		private static AssemblyDefinition Compile(string path)
		{
			BooCompiler compiler = new BooCompiler();
			compiler.Parameters.GenerateInMemory = false;
			compiler.Parameters.Pipeline = PipelineFactory.Compile();
			compiler.Parameters.Input.Add(new FileInput(path));
			compiler.Parameters.OutputAssembly =
				Path.Combine(Path.GetTempPath(), Path.GetFileNameWithoutExtension(path) + ".exe");
			CompilerContext result = compiler.Run();
			CompilerAssert.NoErrors(result);
			return AssemblyFactory.GetAssembly(compiler.Parameters.OutputAssembly);
		}

		private static SR.Assembly CompileToMemory(string code)
		{
			BooCompiler compiler = new BooCompiler();
			compiler.Parameters.GenerateInMemory = true;
			compiler.Parameters.OutputType = CompilerOutputType.Library;
			compiler.Parameters.Pipeline = PipelineFactory.Compile();
			compiler.Parameters.Input.Add(new StringInput("CompileToMemory", code));
			CompilerContext result = compiler.Run();
			CompilerAssert.NoErrors(result);
			return result.GeneratedAssembly;
		}

		private static string GetTestCasePath(string file)
		{
			return Path.Combine(FindTestcasesDirectory(), file);
		}

		private static string FindTestcasesDirectory()
		{
			string currentPath = Environment.CurrentDirectory;
			while (!Directory.Exists(Path.Combine(currentPath, "testcases")))
			{
				string oldPath = currentPath;
				currentPath = Path.GetDirectoryName(currentPath);
				Assert.AreNotEqual(oldPath, currentPath);
			}
			return Path.Combine(currentPath, "testcases/boo");
		}
	}
}
